/*
 * sys-spi-flash.c
 *
 * Copyright(c) 2007-2019 Jianjun Jiang <8192542@qq.com>
 * Official site: http://xboot.org
 * Mobile phone: +86-18665388956
 * QQ: 8192542
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <io.h>

enum {
	SPI_GCR	= 0x04,
	SPI_TCR	= 0x08,
	SPI_IER	= 0x10,
	SPI_ISR	= 0x14,
	SPI_FCR	= 0x18,
	SPI_FSR	= 0x1c,
	SPI_WCR	= 0x20,
	SPI_CCR	= 0x24,
	SPI_MBC	= 0x30,
	SPI_MTC	= 0x34,
	SPI_BCC	= 0x38,
	SPI_TXD	= 0x200,
	SPI_RXD	= 0x300,
};

static inline void sdelay(int loops){
	__asm__ __volatile__ ("1:\n" "subs %0, %1, #1\n"
		"bne 1b":"=r" (loops):"0"(loops));
}

void sys_spi_flash_deinit(void){
	write32(0x1c202c0,0x4000);
	write32(0x1c20060,0x4000);
	write32(0x1c05024,0x02);
	write32(0x1c05004,0x80);
	sdelay(10000);
	write32(0x1c05008,0x87);
	write32(0x1c05018,0x400001);
}

void sys_spi_flash_init2(void){
	uint32_t addr;
	uint32_t val;

	/* Config GPIOC0, GPIOC1, GPIOC2 and GPIOC3 */
	addr = 0x01c20848 + 0x00;
	val = read32(addr);
	val &= ~(0xf << ((0 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((0 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((1 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((1 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((2 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((2 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((3 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((3 & 0x7) << 2));
	write32(addr, val);

	/* Deassert spi0 reset */
	addr = 0x01c202c0;
	val = read32(addr);

	print_string("1:");
	print_hex(val);

	val |= (1 << 20);
	write32(addr, val);

	print_string("2:");
	print_hex(val);

	/* Open the spi0 bus gate */
	addr = 0x01c20000 + 0x60;
	val = read32(addr);

	print_string("3:");
	print_hex(val);
	
	val |= (1 << 20);
	write32(addr, val);
	
	print_string("4:");
	print_hex(val);
	
	/* Set spi clock rate control register, divided by 4 */
	addr = 0x01c05000;
	val=read32(addr+SPI_CCR);

	print_string("5:");
	print_hex(val);
	
	write32(addr + SPI_CCR, 0x00001001);
	//修改SPI速度为100M
	write32(addr + SPI_CCR, 0x00001000);
	/* Enable spi0 and do a soft reset */

	val=read32(addr+SPI_CCR);

	print_string("6:");
	print_hex(val);

	addr = 0x01c05000;
	val = read32(addr + SPI_GCR);

	print_string("7:");
	print_hex(val);

	val |= (1 << 31) | (1 << 7) | (1 << 1) | (1 << 0);
	write32(addr + SPI_GCR, val);

	print_string("8:");
	print_hex(val);
	while(read32(addr + SPI_GCR) & (1 << 31));

	val = read32(addr + SPI_TCR);

	print_string("9:");
	print_hex(val);

	val &= ~(0x3 << 0);
	val |= (1 << 6) | (1 << 2);
	write32(addr + SPI_TCR, val);

	print_string("10:");
	print_hex(val);

	val = read32(addr + SPI_FCR);

	print_string("11:");
	print_hex(val);

	val |= (1 << 31) | (1 << 15);
	write32(addr + SPI_FCR, val);

	print_string("12:");
	print_hex(val);
}

void sys_spi_flash_init(void){
	uint32_t addr;
	uint32_t val;

	/* Config GPIOC0, GPIOC1, GPIOC2 and GPIOC3 */
	addr = 0x01c20848 + 0x00;
	val = read32(addr);
	val &= ~(0xf << ((0 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((0 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((1 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((1 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((2 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((2 & 0x7) << 2));
	write32(addr, val);

	val = read32(addr);
	val &= ~(0xf << ((3 & 0x7) << 2));
	val |= ((0x2 & 0x7) << ((3 & 0x7) << 2));
	write32(addr, val);

	/* Deassert spi0 reset */
	addr = 0x01c202c0;
	val = read32(addr);
	val |= (1 << 20);
	write32(addr, val);

	/* Open the spi0 bus gate */
	addr = 0x01c20000 + 0x60;
	val = read32(addr);
	val |= (1 << 20);
	write32(addr, val);

	/* Set spi clock rate control register, divided by 4 */
	addr = 0x01c05000;

	// 这里读取的频率需要根据 flash 频率确定使用50M 还是100M ，如果不支持 会造成数据读取错误 本人使用的 Winbond W25Q128JVSIQ 连续数据传输速率66MB/S 选用50M
	// AHB_CLK = 200M 
	//bit12-> 0: Select Clock Divide Rate 1  bit12->1: Select Clock Divide Rate 2
	// [11:8]CDR1 bit12=0  SPI_CLK = AHB_CLK / 2^(n+1)
	// [7 :0]CDR2 bit12=1  SPI_CLK = AHB_CLK / (2*(n + 1))  0x00001001=200/(4)  0x00001000=200/(2) 
	//修改SPI速度为 50M 
	write32(addr + SPI_CCR, 0x00001001);
	//修改SPI速度为 100M
	//write32(addr + SPI_CCR, 0x00001000);

	/* Enable spi0 and do a soft reset */
	addr = 0x01c05000;
	val = read32(addr + SPI_GCR);
	val |= (1 << 31) | (1 << 7) | (1 << 1) | (1 << 0);
	write32(addr + SPI_GCR, val);
	while(read32(addr + SPI_GCR) & (1 << 31));

	val = read32(addr + SPI_TCR);
	val &= ~(0x3 << 0);
	val |= (1 << 6) | (1 << 2);
	write32(addr + SPI_TCR, val);

	val = read32(addr + SPI_FCR);
	val |= (1 << 31) | (1 << 15);
	write32(addr + SPI_FCR, val);
}

void sys_spi_flash_exit(void){
	uint32_t addr = 0x01c05000;
	uint32_t val;

	/* Disable the spi0 controller */
	val = read32(addr + SPI_GCR);
	val &= ~((1 << 1) | (1 << 0));
	write32(addr + SPI_GCR, val);
}

static void sys_spi_select(void){
	uint32_t addr = 0x01c05000;
	uint32_t val;

	val = read32(addr + SPI_TCR);
	val &= ~((0x3 << 4) | (0x1 << 7));
	val |= ((0 & 0x3) << 4) | (0x0 << 7);
	write32(addr + SPI_TCR, val);
}

static void sys_spi_deselect(void){
	uint32_t addr = 0x01c05000;
	uint32_t val;

	val = read32(addr + SPI_TCR);
	val &= ~((0x3 << 4) | (0x1 << 7));
	val |= ((0 & 0x3) << 4) | (0x1 << 7);
	write32(addr + SPI_TCR, val);
}

static void sys_spi_write_txbuf(uint8_t * buf, int len){
	uint32_t addr = 0x01c05000;
	int i;

	if(!buf)
		len = 0;

	write32(addr + SPI_MTC, len & 0xffffff);
	write32(addr + SPI_BCC, len & 0xffffff);
	for(i = 0; i < len; ++i)
		write8(addr + SPI_TXD, *buf++);
}

static int sys_spi_transfer(void * txbuf, void * rxbuf, int len){
	uint32_t addr = 0x01c05000;
	int count = len;
	uint8_t * tx = txbuf;
	uint8_t * rx = rxbuf;
	uint8_t val;
	int n, i;

	while(count > 0){
		n = (count <= 64) ? count : 64;
		write32(addr + SPI_MBC, n);
		sys_spi_write_txbuf(tx, n);
		write32(addr + SPI_TCR, read32(addr + SPI_TCR) | (1 << 31));

		while((read32(addr + SPI_FSR) & 0xff) < n);
		for(i = 0; i < n; i++)
		{
			val = read8(addr + SPI_RXD);
			if(rx)
				*rx++ = val;
		}

		if(tx)
			tx += n;
		count -= n;
	}
	return len;
}

static int sys_spi_write_then_read(void * txbuf, int txlen, void * rxbuf, int rxlen){
	if(sys_spi_transfer(txbuf, NULL, txlen) != txlen)
		return -1;
	if(sys_spi_transfer(NULL, rxbuf, rxlen) != rxlen)
		return -1;
	return 0;
}

int sys_spi_flash_read(int addr, void * buf, int count){
	int r;
	uint8_t tx[4];

	tx[0] = 0x03;
	tx[1] = (uint8_t)(addr >> 16);
	tx[2] = (uint8_t)(addr >> 8);
	tx[3] = (uint8_t)(addr >> 0);
	sys_spi_select();
	r = sys_spi_write_then_read(tx, 4, buf, count);
	sys_spi_deselect();

	return r;
}
